#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/errno.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/delay.h>

static dev_t  led_dev_id;
static struct cdev led_cdev;
static struct class *led_class;
static struct device *led_device;

static int *led_pins;
static int led_num;

static ssize_t led_single_write(struct file *filp, const char __user *buf, size_t buf_size, loff_t *offset)
{
	int ret = 0;
	int led_no, val;
	char tmp[2];
	int i;

	ret = copy_from_user(tmp, buf, 2);
	if (ret < 0) {
		return -EFAULT;
	}

	led_no = tmp[0] - '0';
	val = tmp[1] - '0';
	val = !val;

	if ((led_no >= 0) && (led_no < led_num)) {
		gpio_direction_output(led_pins[led_no], val);
		ret = buf_size;
	} else if (led_no >= led_num) {
		for (i = 0; i < led_num; i++) {
			gpio_direction_output(led_pins[i], val);
			ret = buf_size;
		}
	}

	if (ret == 0) {
		ret = -EINVAL;
	}

	return ret;
}

static struct file_operations led_file_ops = {
	.write = led_single_write,
};

static int led_probe(struct platform_device *pdev)
{
	struct resource *res;
	int ret;
	int i;

	led_num = pdev->num_resources;
	led_pins = (int *)kzalloc(sizeof(int) * led_num, GFP_KERNEL);

	for (i = 0; i < led_num; i++) {
		res = platform_get_resource(pdev, IORESOURCE_IO, i);
		led_pins[i] = res->start;
	}

	ret = alloc_chrdev_region(&led_dev_id, 0, 1, "led_single");
	if (ret < 0) {
		printk("regist device id failed.\n");
		goto err_1;
	}

	cdev_init(&led_cdev, &led_file_ops);
	ret = cdev_add(&led_cdev, led_dev_id, 1);
	if (ret < 0) {
		printk("cdev add failed.\n");
		goto err_2;
	}

	led_class = class_create(THIS_MODULE, "led_single");
	//led_device = device_create(led_class, NULL, led_dev_id, NULL, "leds");
	led_device = device_create(led_class, NULL, led_dev_id, NULL, pdev->name);

	for (i = 0; i < led_num; i++) {
		ret = gpio_request(led_pins[i], "LED");
		if (ret) {
			printk("%s: request GPIO %d for LED failed, ret = %d\n", "leds", led_pins[i], ret);
			return ret;
		}

		gpio_direction_output(led_pins[i], 0);
		msleep(20);
		gpio_direction_output(led_pins[i], 1);
	}

err_2:
	unregister_chrdev_region(led_dev_id, 1);	
err_1:
	return ret;
}

static int led_remove(struct platform_device *pdev)
{
	int i;

	for (i = led_num - 1; i >= 0; i--) {
		gpio_direction_output(led_pins[i], 0);
		msleep(20);
		gpio_direction_output(led_pins[i], 1);
		gpio_free(led_pins[i]);
	}

	device_destroy(led_class, led_dev_id);
	class_destroy(led_class);
	cdev_del(&led_cdev);
	unregister_chrdev_region(led_dev_id, 1);

	kfree(led_pins);

	return 0;
}

static int led_suspend(struct device *devp)
{
	printk("<1>=-=-=-=-=-=-= %s -=-=-=-=-=\n", __FUNCTION__);

	return 0;
}

static int led_resume(struct device *devp)
{
	printk("<1>=-=-=-=-=-=-= %s -=-=-=-=-=\n", __FUNCTION__);

	return 0;
}

static struct dev_pm_ops leds_pm = {
	.suspend	= led_suspend,
	.resume		= led_resume,
};

static struct platform_device_id led_id[] = {
	{"fuck"},
	{"shit"},
	{"xiba"},
	{"leds"},
};

static struct platform_driver led_driver = {
	.probe		= led_probe,
	.remove		= led_remove,
	.id_table	= led_id,
	.driver		= {
		.name	= "leds",
		.pm	= &leds_pm,
	},
};

static int __init led_single_drv_init(void)
{
	return platform_driver_register(&led_driver);
}

static void __exit led_single_drv_exit(void)
{
	platform_driver_unregister(&led_driver);
}

module_init(led_single_drv_init);
module_exit(led_single_drv_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YYW");
